package org.amphiprion.rfreplayanalyser.ui.lapanalyzer;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;

import org.amphiprion.rfreplayanalyser.parser.DriverLocation;
import org.amphiprion.rfreplayanalyser.parser.Replay;
import org.amphiprion.rfreplayanalyser.parser.ReplayInfo;
import org.amphiprion.rfreplayanalyser.util.MathUtil;

@SuppressWarnings("serial")
public class LapDrawerPanel extends JPanel {
	private float zoom = 1;
	private LapProvider lapProvider;
	private float elapsedLaptime = 0;

	public enum MouseMode {
		SELECT, ZOOM, START_ANALYZER_POINT, END_ANALYZER_POINT
	}

	public enum DisplayMode {
		ANIMATION
	}

	private DisplayMode displayMode;
	private MouseMode mouseMode;
	private float circuitWidthX = 0;
	private float circuitWidthY = 0;

	/**
	 * Create the panel.
	 */
	public LapDrawerPanel(final LapProvider lapProvider) {
		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(final MouseEvent arg0) {
				if (mouseMode == MouseMode.ZOOM) {
					if (arg0.getButton() == 1) {
						setZoom(zoom + 0.25f);
					} else if (arg0.getButton() == 3) {
						setZoom(zoom - 0.25f);
					}
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							lapProvider.updateScrollbarPositionToCenterAt(arg0.getX(), arg0.getY());
						}
					});
				} else if (mouseMode == MouseMode.START_ANALYZER_POINT || mouseMode == MouseMode.END_ANALYZER_POINT) {
					int px = arg0.getX();
					int py = arg0.getY();

					Replay replay = lapProvider.getReplay();
					ReplayInfo info = replay.getInfo();

					circuitWidthX = info.getMaxPosX() - info.getMinPosX();
					circuitWidthY = info.getMaxPosY() - info.getMinPosY();
					float ratio = Math.min(((JViewport) getParent()).getWidth() / circuitWidthX, ((JViewport) getParent()).getHeight() / circuitWidthY);
					float realX = px / (ratio * zoom) + info.getMinPosX();
					float realY = py / (ratio * zoom) + info.getMinPosY();
					findStartPoint(realX, realY);
				} else {
					// selection de la courbe
					int px = arg0.getX();
					int py = arg0.getY();

					Replay replay = lapProvider.getReplay();
					ReplayInfo info = replay.getInfo();

					circuitWidthX = info.getMaxPosX() - info.getMinPosX();
					circuitWidthY = info.getMaxPosY() - info.getMinPosY();
					float ratio = Math.min(((JViewport) getParent()).getWidth() / circuitWidthX, ((JViewport) getParent()).getHeight() / circuitWidthY);
					float realX = px / (ratio * zoom) + info.getMinPosX();
					float realY = py / (ratio * zoom) + info.getMinPosY();
					findDriver(realX, realY);

				}
			}
		});
		this.lapProvider = lapProvider;

	}

	float startAnalyzerMouseX;
	float startAnalyzerMouseY;
	float endAnalyzerMouseX;
	float endAnalyzerMouseY;
	float startAnalyzerX;
	float startAnalyzerY;
	float[] startAnalyzerTime;
	float endAnalyzerX;
	float endAnalyzerY;
	float[] endAnalyzerTime;

	public void recomputeAnalyzerPoints() {
		MouseMode mm = mouseMode;
		if (startAnalyzerTime != null) {
			mouseMode = MouseMode.START_ANALYZER_POINT;
			findStartPoint(startAnalyzerMouseX, startAnalyzerMouseY);
		}
		if (endAnalyzerTime != null) {
			mouseMode = MouseMode.END_ANALYZER_POINT;
			findStartPoint(endAnalyzerMouseX, endAnalyzerMouseY);
		}
		mouseMode = mm;
	}

	private void findStartPoint(float realX, float realY) {
		if (mouseMode == MouseMode.START_ANALYZER_POINT) {
			startAnalyzerTime = null;
			if (lapProvider.getLapItems().size() == 0) {
				lapProvider.analyzerRangeUpdated(startAnalyzerTime, endAnalyzerTime);
				return;
			}
			startAnalyzerMouseX = realX;
			startAnalyzerMouseY = realY;
			startAnalyzerTime = new float[lapProvider.getLapItems().size()];
		} else if (mouseMode == MouseMode.END_ANALYZER_POINT) {
			endAnalyzerTime = null;
			if (lapProvider.getLapItems().size() == 0) {
				lapProvider.analyzerRangeUpdated(startAnalyzerTime, endAnalyzerTime);
				return;
			}
			endAnalyzerMouseX = realX;
			endAnalyzerMouseY = realY;
			endAnalyzerTime = new float[lapProvider.getLapItems().size()];
		}
		// trouver les deux driverLocation encadrant la selection
		Replay replay = lapProvider.getReplay();
		ReplayInfo info = replay.getInfo();

		circuitWidthX = info.getMaxPosX() - info.getMinPosX();
		circuitWidthY = info.getMaxPosY() - info.getMinPosY();

		int lapItemIndex = 0;
		for (LapItem lapItem : lapProvider.getLapItems()) {

			boolean first = true;
			float lastX = 0;
			float lastY = 0;
			float lastReplaytime = 0;
			float startReplaytime = 0;
			float dist = Float.MAX_VALUE;

			List<DriverLocation> locs = replay.getDriverLocations(lapItem.driverId);
			for (DriverLocation loc : locs) {
				if (loc.getLap() == lapItem.lap) {
					float newX = loc.getPosX();
					float newY = loc.getPosY();
					float newReplayTime = loc.getReplayTime();

					if (!first) {
						for (int i = 0; i <= 10; i++) {
							float ratioInside = 0.1f * i; // from 0 to 1
							float tx = lastX + (newX - lastX) * ratioInside;
							float ty = lastY + (newY - lastY) * ratioInside;
							float newDist = (tx - realX) * (tx - realX) + (ty - realY) * (ty - realY);
							if (newDist < dist) {
								dist = newDist;
								if (mouseMode == MouseMode.START_ANALYZER_POINT) {
									startAnalyzerX = tx;
									startAnalyzerY = ty;
									startAnalyzerTime[lapItemIndex] = lastReplaytime + (newReplayTime - lastReplaytime) * ratioInside;
								} else if (mouseMode == MouseMode.END_ANALYZER_POINT) {
									endAnalyzerX = tx;
									endAnalyzerY = ty;
									endAnalyzerTime[lapItemIndex] = lastReplaytime + (newReplayTime - lastReplaytime) * ratioInside;
								}
							}
						}

					} else {
						first = false;
						startReplaytime = newReplayTime;
					}

					lastX = loc.getPosX();
					lastY = loc.getPosY();
					lastReplaytime = newReplayTime;
				}
			}
			lapItemIndex++;
		}
		revalidate();
		repaint();
		lapProvider.analyzerRangeUpdated(startAnalyzerTime, endAnalyzerTime);
	}

	public void setDisplayMode(DisplayMode mode) {
		displayMode = mode;
		elapsedLaptime = 0;
		revalidate();
		repaint();
	}

	public void setLaptimeElapsed(float elapsedLaptime) {
		this.elapsedLaptime = elapsedLaptime;
		revalidate();
		repaint();
	}

	@Override
	public void paint(Graphics g) {
		float[] replayTimeAtCarPosition = new float[lapProvider.getLapItems().size()];
		super.paint(g);
		Replay replay = lapProvider.getReplay();
		ReplayInfo info = replay.getInfo();
		Color lineColor;
		circuitWidthX = info.getMaxPosX() - info.getMinPosX();
		circuitWidthY = info.getMaxPosY() - info.getMinPosY();
		int viewPortWidth = ((JViewport) getParent()).getWidth();
		int viewPortHeight = ((JViewport) getParent()).getHeight();
		float ratio = Math.min(viewPortWidth / circuitWidthX, viewPortHeight / circuitWidthY);
		int lapItemIndex = 0;
		for (LapItem lapItem : lapProvider.getLapItems()) {
			if (displayMode == DisplayMode.ANIMATION && !lapProvider.isLapItemSelected(lapItem)) {
				lineColor = new Color(lapItem.color.getRed(), lapItem.color.getGreen(), lapItem.color.getBlue(), 50);
			} else {
				lineColor = lapItem.color;
			}
			g.setColor(lineColor);

			boolean first = true;
			int lastX = 0;
			int lastY = 0;
			float lastReplaytime = 0;
			float startReplaytime = 0;
			List<DriverLocation> locs = replay.getDriverLocations(lapItem.driverId);
			for (DriverLocation loc : locs) {
				if (loc.getLap() == lapItem.lap) {
					int newX = (int) ((loc.getPosX() - info.getMinPosX()) * ratio * zoom);
					int newY = (int) ((loc.getPosY() - info.getMinPosY()) * ratio * zoom);
					float newReplayTime = loc.getReplayTime();
					if (!first) {
						g.drawLine(lastX, lastY, newX, newY);

						if (displayMode == DisplayMode.ANIMATION) {
							if (newReplayTime >= startReplaytime + elapsedLaptime && lastReplaytime <= startReplaytime + elapsedLaptime) {
								float timeratio = (startReplaytime + elapsedLaptime - lastReplaytime) / (newReplayTime - lastReplaytime);
								int carX = (int) (lastX + (newX - lastX) * timeratio);
								int carY = (int) (lastY + (newY - lastY) * timeratio);
								g.setColor(lapItem.color);
								if (!lapProvider.isLapItemSelected(lapItem)) {
									g.fillArc(carX - 5, carY - 5, 10, 10, 0, 360);
								} else {
									g.fillRect(carX - 5, carY - 5, 10, 10);
									g.setColor(Color.BLACK);
									g.drawRect(carX - 5, carY - 5, 10, 10);
								}
								g.setColor(lineColor);
								replayTimeAtCarPosition[lapItemIndex] = startReplaytime + elapsedLaptime;
								if (lapProvider.isLapItemSelected(lapItem)) {
									lapProvider.updateScreenPositionSelectedCar(new Point(carX, carY));
									// ((JViewport)
									// getParent()).setViewPosition(new
									// Point(carX - viewPortWidth / 2, carY -
									// viewPortHeight / 2));

								}
							}
						}

					} else {
						first = false;
						startReplaytime = newReplayTime;
					}

					lastX = newX;
					lastY = newY;
					lastReplaytime = newReplayTime;
				}
			}
			lapItemIndex++;
		}
		if (startAnalyzerTime != null) {
			g.setColor(Color.DARK_GRAY);
			int startAnalyzerScreenX = (int) ((startAnalyzerX - info.getMinPosX()) * ratio * zoom);
			int startAnalyzerScreenY = (int) ((startAnalyzerY - info.getMinPosY()) * ratio * zoom);
			g.drawRect(startAnalyzerScreenX - 5, startAnalyzerScreenY - 5, 10, 10);
		}
		if (endAnalyzerTime != null) {
			g.setColor(Color.DARK_GRAY);
			int endAnalyzerScreenX = (int) ((endAnalyzerX - info.getMinPosX()) * ratio * zoom);
			int endAnalyzerScreenY = (int) ((endAnalyzerY - info.getMinPosY()) * ratio * zoom);
			g.drawRect(endAnalyzerScreenX - 5, endAnalyzerScreenY - 5, 10, 10);
		}
		lapProvider.updateReplayTimeForCars(replayTimeAtCarPosition);
	}

	public void setMouseMode(MouseMode mode) {
		this.mouseMode = mode;
		if (mode == MouseMode.ZOOM) {
			// Cursor cursor =
			// Toolkit.getDefaultToolkit().createCustomCursor(img, hotSpot,
			// name);
			// component.setCursor(cursor);

			setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
		} else {
			setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		}
	}

	private void setZoom(float zoom) {
		this.zoom = Math.max(1, zoom);
		float ratio = Math.min(((JViewport) getParent()).getWidth() / circuitWidthX, ((JViewport) getParent()).getHeight() / circuitWidthY);
		this.setPreferredSize(new Dimension((int) (circuitWidthX * ratio * zoom), (int) (circuitWidthY * ratio * zoom)));
		this.setMinimumSize(new Dimension((int) (circuitWidthX * ratio * zoom), (int) (circuitWidthY * ratio * zoom)));
		((JViewport) getParent()).revalidate();
		((JViewport) getParent()).repaint();
		// revalidate();
		// repaint();
	}

	private void findDriver(float posX, float posY) {
		Replay replay = lapProvider.getReplay();
		double[] pA = new double[2];
		double[] pB = new double[2];
		double[] pM = new double[] { posX, posY };
		LapItem foundLapItem = null;
		double minDist = Double.MAX_VALUE;
		for (LapItem lapItem : lapProvider.getLapItems()) {

			boolean first = true;
			List<DriverLocation> locs = replay.getDriverLocations(lapItem.driverId);
			for (DriverLocation loc : locs) {
				if (loc.getLap() == lapItem.lap) {
					pB[0] = loc.getPosX();
					pB[1] = loc.getPosY();
					if (!first) {
						double dist = MathUtil.LineToPointDistance2D(pA, pB, pM, true);
						if (dist < minDist) {
							minDist = dist;
							foundLapItem = lapItem;
						}
					} else {
						first = false;
					}

					pA[0] = pB[0];
					pA[1] = pB[1];
				}
			}
		}
		if (foundLapItem != null) {
			lapProvider.lapItemSelected(foundLapItem, false);
		}
	}
}
